use std::{f32::consts::PI, rc::Rc};

use bliquez::{
    material::attribute::Uniform, mesh::MeshInstance, shader::Shader, Brick, Context, Material,
    Scene, Vertex,
};
use cgmath::{ortho, vec2, vec3, vec4, Matrix4, Rad, SquareMatrix, Vector2, Vector4};
use wgpu::{include_wgsl, vertex_attr_array};
use winit::{
    dpi::PhysicalSize,
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

const COLOR_RED: Vector4<f32> = vec4(1.0, 0.0, 0.0, 1.0);
const COLOR_GREEN: Vector4<f32> = vec4(0.0, 1.0, 0.0, 1.0);
const COLOR_BLUE: Vector4<f32> = vec4(0.0, 0.0, 1.0, 1.0);

fn main() {
    let el = EventLoop::new().unwrap();
    let window = WindowBuilder::new()
        .with_title("Hello, world")
        .with_inner_size(PhysicalSize {
            width: 800,
            height: 800,
        })
        .build(&el)
        .unwrap();

    let mut ctx = Context::new(&window, 800, 800);

    let mut scene = Scene::new(wgpu::Color::BLACK);
    scene.add_brick("hello", HelloWorld::new(&ctx));

    let mut theta = 0.0;

    el.set_control_flow(ControlFlow::Poll);
    el.run(|event, target| match event {
        Event::WindowEvent {
            event: WindowEvent::CloseRequested,
            ..
        } => target.exit(),
        Event::AboutToWait => {
            theta += PI / 1440.0;
            let hw = scene.get_brick_mut::<HelloWorld>("hello").unwrap();
            hw.material
                .attr_mut::<Uniform<Matrix4<f32>>>(0)
                .unwrap()
                .set_data(
                    ortho(-1.0, 1.0, -1.0, 1.0, -10.0, 10.0)
                        * Matrix4::from_translation(vec3(0.0, 0.0, -5.0))
                        * Matrix4::from_angle_y(Rad(theta)),
                );
            window.request_redraw();
        }
        Event::WindowEvent {
            event: WindowEvent::KeyboardInput { event, .. },
            ..
        } => if event.state.is_pressed() {},
        Event::WindowEvent {
            event: WindowEvent::Resized(size),
            ..
        } => {
            ctx.resize(size.width, size.height);
        }
        Event::WindowEvent {
            event: WindowEvent::RedrawRequested,
            ..
        } => ctx.render_scene(&mut scene),
        _ => (),
    })
    .unwrap();
}

#[repr(C)]
#[derive(Clone, Copy)]
struct HelloVertex {
    position: Vector2<f32>,
    color: Vector4<f32>,
}

impl Vertex for HelloVertex {
    const ATTRS: &'static [wgpu::VertexAttribute] =
        &vertex_attr_array![0 => Float32x2, 1 => Float32x4];
}

fn vtx(x: f32, y: f32, color: Vector4<f32>) -> HelloVertex {
    HelloVertex {
        position: vec2(x, y),
        color,
    }
}

struct HelloWorld {
    pipeline: Rc<wgpu::RenderPipeline>,
    material: Material,
    mesh: MeshInstance<HelloVertex>,
}

impl HelloWorld {
    fn new(ctx: &bliquez::Context) -> Self {
        let uniform = Uniform::new(ctx, Matrix4::<f32>::identity());

        let material = Material::builder()
            .attr(uniform, wgpu::ShaderStages::VERTEX)
            .build(ctx);

        let layout = ctx.create_pipeline_layout(&[material.layout()]);
        let mut shader = Shader::new(ctx, include_wgsl!("helloworld.wgsl"));
        shader.add_vertex_layout::<HelloVertex>();
        shader.add_screen_color_target(ctx);

        let pipeline = ctx.create_simple_render_pipeline(
            &layout,
            &shader,
            wgpu::PrimitiveState {
                ..Default::default()
            },
        );

        let mut mesh = MeshInstance::new(ctx);

        mesh.add_triangle([
            vtx(-0.5, -0.5, COLOR_RED),
            vtx(0.5, -0.5, COLOR_GREEN),
            vtx(0.0, 0.5, COLOR_BLUE),
        ]);

        Self {
            pipeline: Rc::new(pipeline),
            material,
            mesh,
        }
    }
}

impl Brick for HelloWorld {
    fn prepare(&mut self, ctx: &bliquez::Context) {
        self.material.prepare(ctx);
        self.mesh.prepare(ctx);
    }

    fn render<'pass>(&'pass self, pass: &mut wgpu::RenderPass<'pass>) {
        pass.set_pipeline(&self.pipeline);
        pass.set_bind_group(0, self.material.group(), &[]);
        self.mesh.render(pass);
    }
}
